{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# pytorch简介"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "from mpl_toolkits.mplot3d import Axes3D\n",
    "\n",
    "import torch\n",
    "import torch.nn as nn\n",
    "import torch.nn.functional as F"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 随机数设置种子\n",
    "torch.manual_seed(123)\n",
    "np.random.seed(123)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "神经网络模型\n",
    "\n",
    "$y = f(x;\\theta)$\n",
    "\n",
    "$L = Loss(y,t)$\n",
    "\n",
    "$\\min L(\\theta)$\n",
    "\n",
    "$\\theta^{k+1} = \\theta^k-\\eta \\nabla L(\\theta^k)\\ k=0,1,2,...$"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## torch tensor and numpy ndarray"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "x_numpy = [0.1 0.2 0.3], y_numpy = [1. 2. 3.]\n",
      "x_torch = tensor([0.1000, 0.2000, 0.3000]), y_torch = tensor([1., 2., 3.])\n"
     ]
    }
   ],
   "source": [
    "x_numpy = np.array([0.1, 0.2, 0.3]) # 通过列表构造一个数组\n",
    "y_numpy = np.array([1., 2., 3.])\n",
    "\n",
    "x_torch = torch.tensor([0.1, 0.2, 0.3])\n",
    "y_torch = torch.tensor([1., 2., 3.])\n",
    "print(f'x_numpy = {x_numpy}, y_numpy = {y_numpy}')\n",
    "print(f'x_torch = {x_torch}, y_torch = {y_torch}')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "2\n",
      "tensor([0.1000, 0.2000, 0.3000], requires_grad=True)\n"
     ]
    }
   ],
   "source": [
    "#dir(x_numpy) # 包含了大量的方法\n",
    "#print(x_numpy.argmax())\n",
    "#dir(x_torch) # 多出来一些方法\n",
    "#x_torch = torch.tensor([0.1,0.2,0.3],requires_grad=True)\n",
    "#print(x_torch)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 相互转换"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "tensor([0.1000, 0.2000, 0.3000], dtype=torch.float64)\n",
      "[0.1 0.2 0.3]\n"
     ]
    }
   ],
   "source": [
    "print(torch.from_numpy(x_numpy))\n",
    "print(x_torch.numpy())\n",
    "# matplotlib只能处理numpy数组绘图"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 四则运算"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[1.1 2.2 3.3] tensor([1.1000, 2.2000, 3.3000])\n",
      "[-0.9 -1.8 -2.7] tensor([-0.9000, -1.8000, -2.7000])\n",
      "[0.1 0.4 0.9] tensor([0.1000, 0.4000, 0.9000])\n",
      "[0.1 0.1 0.1] tensor([0.1000, 0.1000, 0.1000])\n"
     ]
    }
   ],
   "source": [
    "print(x_numpy + y_numpy, x_torch + y_torch)\n",
    "print(x_numpy - y_numpy, x_torch - y_torch)\n",
    "print(x_numpy * y_numpy, x_torch * y_torch)\n",
    "print(x_numpy / y_numpy, x_torch / y_torch)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 范数(norm)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "0.37416573867739417 tensor(0.3742)\n"
     ]
    }
   ],
   "source": [
    "print(np.linalg.norm(x_numpy), torch.norm(x_torch))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 对某个维度做操作"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "2.5 tensor(2.5000)\n",
      "[2. 3.] tensor([2., 3.])\n",
      "[1.5 3.5] tensor([1.5000, 3.5000])\n"
     ]
    }
   ],
   "source": [
    "x_numpy = np.array([[1, 2], \n",
    "                    [3, 4]]).astype(float)\n",
    "x_torch = torch.tensor([[1, 2],\n",
    "                        [3, 4]]).float()\n",
    "print(np.mean(x_numpy), torch.mean(x_torch))\n",
    "# numpy用axis指定，pytorch用dim指定\n",
    "print(np.mean(x_numpy, axis=0), torch.mean(x_torch, dim=0)) # 按列\n",
    "print(np.mean(x_numpy, axis=1), torch.mean(x_torch, dim=1)) # 按行"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "10.0 tensor(10.)\n",
      "[4. 6.] tensor([4., 6.])\n",
      "[3. 7.] tensor([3., 7.])\n"
     ]
    }
   ],
   "source": [
    "print(np.sum(x_numpy), torch.sum(x_torch))\n",
    "print(np.sum(x_numpy, axis=0), torch.sum(x_torch, dim=0))\n",
    "print(np.sum(x_numpy, axis=1), torch.sum(x_torch, dim=1))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 改变形状"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 53,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "4 4\n",
      "(1000, 3, 28, 28) torch.Size([1000, 3, 28, 28])\n",
      "(1000, 3, 784) torch.Size([1000, 3, 784])\n",
      "(3000, 28, 28) torch.Size([3000, 28, 28])\n"
     ]
    }
   ],
   "source": [
    "N, C, H, W = 1000, 3, 28, 28 # 类似元组打包解包功能\n",
    "# N: 每次送到GPU中计算的图象数\n",
    "# C: 图象通道数量\n",
    "# H; 图象高度\n",
    "# W: 图象宽度\n",
    "\n",
    "X_numpy = np.random.randn(N, C, H, W) # 创建N*X*H*W规模的数组\n",
    "X_torch = torch.randn((N, C, H, W))\n",
    "\n",
    "print(X_numpy.ndim, X_torch.ndim)\n",
    "print(X_numpy.shape, X_torch.shape)\n",
    "print(X_numpy.reshape(N, C, -1).shape, X_torch.view(N, C, -1).shape) # -1表示第三维自己计算，N*W\n",
    "print(X_numpy.reshape(-1, H, W).shape, X_torch.view(-1, H, W).shape) # -1表示第一维自己计算，N*C"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 增加维度"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(1, 2, 3)\n",
      "(2, 1, 3)\n",
      "(2, 3, 1)\n"
     ]
    }
   ],
   "source": [
    "x_numpy = np.array([[1, 2, 3], \n",
    "                    [2, 3, 4]])\n",
    "print(np.expand_dims(x_numpy, axis=0).shape)\n",
    "print(np.expand_dims(x_numpy, axis=1).shape)\n",
    "print(np.expand_dims(x_numpy, axis=-1).shape)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "torch.Size([1, 2, 3])\n",
      "torch.Size([2, 1, 3])\n",
      "torch.Size([2, 3, 1])\n",
      "torch.Size([1, 2, 3])\n",
      "torch.Size([2, 1, 3])\n",
      "torch.Size([2, 3, 1])\n"
     ]
    }
   ],
   "source": [
    "x_torch = torch.tensor([[1, 2, 3], \n",
    "                        [2, 3, 4]])\n",
    "print(x_torch.unsqueeze(0).shape)\n",
    "print(x_torch.unsqueeze(1).shape)\n",
    "print(x_torch.unsqueeze(-1).shape)\n",
    "\n",
    "print(torch.unsqueeze(x_torch, dim=0).shape)\n",
    "print(torch.unsqueeze(x_torch, dim=1).shape)\n",
    "print(torch.unsqueeze(x_torch, dim=-1).shape)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 减小维度"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(1, 2, 3)\n",
      "(2, 3)\n"
     ]
    }
   ],
   "source": [
    "x_numpy = np.array([[[1, 2, 3], \n",
    "                     [2, 3, 4]]])\n",
    "print(x_numpy.shape)\n",
    "print(np.squeeze(x_numpy).shape)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "torch.Size([1, 2, 3])\n",
      "torch.Size([2, 3])\n",
      "torch.Size([2, 3])\n"
     ]
    }
   ],
   "source": [
    "x_torch = torch.tensor([[[1, 2, 3], \n",
    "                         [2, 3, 4]]])\n",
    "print(x_torch.shape)\n",
    "print(torch.squeeze(x_torch).shape)\n",
    "print(x_torch.squeeze().shape)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 自动微分(Autograd)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "* 计算$e = f(a, b) = (a+b)(b+1)$的梯度\n",
    "$$\n",
    "\\nabla f = \\left(\n",
    "\\begin{array}{c}\n",
    "a + 1 \\\\\n",
    "a+ 2b+1\n",
    "\\end{array}\n",
    "\\right)\n",
    "$$\n",
    "如\n",
    "$$\n",
    "\\nabla f\\big|_{(3, 2)}= \\left(\n",
    "\\begin{array}{c}\n",
    "3 \\\\\n",
    "8\n",
    "\\end{array}\n",
    "\\right)\n",
    "$$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "metadata": {},
   "outputs": [],
   "source": [
    "def f(x):\n",
    "    return (x[0]+x[1])*(x[1]+1)\n",
    "def grad_f(x):\n",
    "    return x[1]+1, x[0] + 2*x[1] + 1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 40,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "analytic grad_f: (tensor(3.7178, grad_fn=<SubBackward0>), tensor(5.0100, grad_fn=<AddBackward0>))\n",
      "autograd grad_f: tensor([3.7178, 5.0100])\n"
     ]
    }
   ],
   "source": [
    "x = torch.tensor([3., 2.], requires_grad=True)\n",
    "e = f(x) # 向前传播\n",
    "e.backward() # 反向传播，整型数不能做反向传播\n",
    "print(f'analytic grad_f: {grad_f(x)}')\n",
    "print(f'autograd grad_f: {x.grad}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<img src='images/3.png'>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<img src='images/4.png'>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "* 令$\\vec x=(x_1, x_2)^T$，计算函数\n",
    "$$\n",
    "f(\\vec x) = 2 x_1x_2 + x_2 \\cos(x_1) \n",
    "$$\n",
    "的梯度\n",
    "$$\n",
    "\\nabla f = \\left(\n",
    "\\begin{array}{c}\n",
    "2x_2 -x_2 \\sin x_1 \\\\\n",
    "2x_1 + \\cos x_1\n",
    "\\end{array}\n",
    "\\right)\n",
    "$$\n",
    "如\n",
    "$$\n",
    "\\nabla f\\big|_{(\\pi, 1)} = \\left(\n",
    "\\begin{array}{c}\n",
    "2 \\\\\n",
    "2\\pi-1\n",
    "\\end{array}\n",
    "\\right)\n",
    "$$\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 45,
   "metadata": {},
   "outputs": [],
   "source": [
    "def f(x):\n",
    "    return 2*x[0]*x[1] + x[1] * torch.cos(x[0])\n",
    "def grad_f(x):\n",
    "    return 2*x[1]-x[1]*torch.sin(x[0]), 2*x[0]+torch.cos(x[0])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 46,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "analytic grad_f: (tensor(2., grad_fn=<SubBackward0>), tensor(5.2832, grad_fn=<AddBackward0>))\n",
      "autograd grad_f: tensor([2.0000, 5.2832])\n"
     ]
    }
   ],
   "source": [
    "x = torch.tensor([np.pi, 1], requires_grad=True)\n",
    "y = f(x)\n",
    "y.backward()\n",
    "\n",
    "print(f'analytic grad_f: {grad_f(x)}')\n",
    "print(f'autograd grad_f: {x.grad}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "* 计算$f(x) = (x-2)^2$的导数$f^\\prime(x)=2(x-2)$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 49,
   "metadata": {},
   "outputs": [],
   "source": [
    "def f(x):\n",
    "    return (x-2)**2\n",
    "\n",
    "def f_prime(x):\n",
    "    return 2*(x-2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 50,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "analytic f_prime: tensor([-2.], grad_fn=<MulBackward0>)\n",
      "autograd f_prime: tensor([-2.])\n"
     ]
    }
   ],
   "source": [
    "x = torch.tensor([1.0], requires_grad=True)\n",
    "y = f(x)\n",
    "y.backward()\n",
    "\n",
    "print(f'analytic f_prime: {f_prime(x)}')\n",
    "print(f'autograd f_prime: {x.grad}')\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 61,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "x = np.linspace(0,4)\n",
    "y = f(x)\n",
    "plt.plot(x,y)\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 利用Autograd算得的梯度实现梯度下降法"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "求函数$f(x)=(x-2)^2$的极小值点。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 62,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "iter\tx\tf(x)\tf'(x)\tf'(x) pytorch\n",
      "0\t1.000\t1.000\t-2.000\t-2.000\n",
      "1\t1.200\t0.640\t-1.600\t-1.600\n",
      "2\t1.360\t0.410\t-1.280\t-1.280\n",
      "3\t1.488\t0.262\t-1.024\t-1.024\n",
      "4\t1.590\t0.168\t-0.819\t-0.819\n",
      "5\t1.672\t0.107\t-0.655\t-0.655\n",
      "6\t1.738\t0.069\t-0.524\t-0.524\n",
      "7\t1.790\t0.044\t-0.419\t-0.419\n",
      "8\t1.832\t0.028\t-0.336\t-0.336\n",
      "9\t1.866\t0.018\t-0.268\t-0.268\n",
      "10\t1.893\t0.012\t-0.215\t-0.215\n",
      "11\t1.914\t0.007\t-0.172\t-0.172\n",
      "12\t1.931\t0.005\t-0.137\t-0.137\n",
      "13\t1.945\t0.003\t-0.110\t-0.110\n",
      "14\t1.956\t0.002\t-0.088\t-0.088\n",
      "15\t1.965\t0.001\t-0.070\t-0.070\n",
      "16\t1.972\t0.001\t-0.056\t-0.056\n",
      "17\t1.977\t0.001\t-0.045\t-0.045\n",
      "18\t1.982\t0.000\t-0.036\t-0.036\n",
      "19\t1.986\t0.000\t-0.029\t-0.029\n"
     ]
    }
   ],
   "source": [
    "x = torch.tensor([1.], requires_grad=True)\n",
    "learning_rate =  0.1\n",
    "\n",
    "print(\"iter\\tx\\tf(x)\\tf'(x)\\tf'(x) pytorch\")\n",
    "for i in range(20):\n",
    "    y = f(x)\n",
    "    y.backward()\n",
    "    \n",
    "    print(f\"{i}\\t{x.item():.3f}\\t{y.item():.3f}\\t{f_prime(x).item():.3f}\\t{x.grad.item():.3f}\")\n",
    "    x.data -= learning_rate * x.grad # 迭代\n",
    "    x.grad.detach_()\n",
    "    x.grad.zero_() # 必须做，否则会把历次的梯度累加，清空x.grad"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.2"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
